ChimneyでRefined typeを含むオブジェクトの変換をする
はじめに
こんにちは、リングフィットアドベンチャーで一番好きなエクササイズはモモアゲアゲの佐々木です。
今回はScaladexで見つけて気になっていたChimneyを試してみました。
Chimneyとは
Chimney は似てるけどちょっと違うフィールドをもつオブジェクト間のフィールドコピーにおけるボイラープレートを削減できるライブラリです。
例えば以下のようにあるオブジェクトから同じ名前、同じ型のフィールドを持つ別のオブジェクトが生成できます。
import io.scalaland.chimney.dsl._ final case class ReadOrderResponse(orderId:String, vat:BigDecimal, totalExVat:BigDecimal) final case class Order(orderId:String, vat:BigDecimal, totalExVat:BigDecimal) val readOrderResponse = ReadOrderResponse("order_001", 30, 300) val order = readOrderResponse.transformInto[Order] println(readOrderResponse) // ReadOrderResponse(order_001,30,300) println(order) // Order(order_001,30,300)
他にもネストしたコレクションの変換やValue Class の変換、フィールド名の変更などかなり柔軟に変換を行えます。公式ドキュメントにはコード例も交えて紹介されているのでわかりやすいです。
Refined型への変換
Chimneyを試していてStringやLongなどの組み込み型からRefined 型への変換をしたくなったので試してみました。
Refined型→Refined型への変換(同じ型どうし)
これは特に工夫することなく行えました。
組み込み型→Refined型への変換
結論からいうとこのパターンではTransformerを定義する必要がありました。具体的には以下の例(String → UserId
)のようになります。
これはRefinedTypeOps#unsafeFrom
を使った一番単純な実装ですが#from
を使うことで例外翻訳することもできると思います。
いずれにしても実行時例外をスローするしかないので設計によってはChimney以外の場所でバリデーションを行なったり、例外をハンドリングする必要があると思います。
import io.scalaland.chimney.Transformer import io.scalaland.chimney.dsl._ import eu.timepit.refined._ import eu.timepit.refined.api._ import eu.timepit.refined.string._ // case class with Refined type field. final case class User(name: UserName, id:UserId) object User { type UserId = String Refined MatchesRegex[W.`"[0-9]+"`.T] object UserId extends RefinedTypeOps[UserId, String] } final case class UserName(firstName:String, lastName:String) // Response from GET User API final case class ReadUserResponse(id:String, firstName:String, lastName:String) val response:ReadUserResponse = ReadUserResponse("123", "firstName", "lastName") //Transformerを定義 implicit val stringToUserId:Transformer[String, UserId] = (src: String) => UserId.unsafeFrom(src) val user:User = response.into[User] .withFieldComputed(_.name, f => UserName(f.firstName, f.lastName)) .transform println(response) // ReadUserResponse(123,firstName,lastName) println(user) // User(UserName(firstName,lastName),123)
最後に
Chimneyを上手く使えばコード量をかなり削減できると思います。